﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Threading;
using System.Xml.Serialization;
using JetBrains.Annotations;

namespace MugenMvvmToolkit.Infrastructure
{
    /// <summary>
    ///     Represents the weak-reference collection, all items added to this collection will be wrapped to a
    ///     <see cref="WeakReference" />.
    /// </summary>
    /// <typeparam name="T">The type of item.</typeparam>
#if WPF
    [Serializable]
#endif
    [DataContract(IsReference = true)]
    public class WeakReferenceCollection<T> : ICollection, ICollection<T> where T : class
    {
        #region Fields

        private List<WeakReference> _weakReferences;
#if WPF
        [NonSerialized]
#endif
        [XmlIgnore]
        private object _syncRoot;

        #endregion

        #region Constructors

        /// <summary>
        ///     Initializes a new instance of the <see cref="WeakReferenceCollection{T}" /> class.
        /// </summary>
        public WeakReferenceCollection()
        {
            _weakReferences = new List<WeakReference>();
        }

        /// <summary>
        ///     Initializes a new instance of the <see cref="WeakReferenceCollection{T}" /> class.
        /// </summary>
        public WeakReferenceCollection([NotNull]IEnumerable<T> items)
            : this()
        {
            Should.NotBeNull(items, "items");
            foreach (T item in items)
                Add(item);
        }

        #endregion

        #region Properties

        /// <summary>
        /// Only for serialization.
        /// </summary>
        [DataMember]
        internal IList<T> WeakReferences
        {
            get { return ToList(); }
            set { _weakReferences = new List<WeakReference>(value.Select(arg => new WeakReference(arg))); }
        }

        #endregion

        #region Implementation of interfaces

        /// <summary>
        ///     Returns an enumerator that iterates through the collection.
        /// </summary>
        public IEnumerator<T> GetEnumerator()
        {
            return ToList().GetEnumerator();
        }

        /// <summary>
        ///     Returns an enumerator that iterates through a collection.
        /// </summary>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        /// <summary>
        ///     Adds an item to the <see cref="WeakReferenceCollection{T}" />.
        /// </summary>
        void ICollection<T>.Add(T item)
        {
            Add(item);
        }

        /// <summary>
        ///     Removes all items from the <see cref="WeakReferenceCollection{T}" />.
        /// </summary>
        public void Clear()
        {
            _weakReferences.Clear();
        }

        /// <summary>
        ///     Determines whether the <see cref="WeakReferenceCollection{T}" /> contains a specific value.
        /// </summary>
        public bool Contains(T item)
        {
            Should.NotBeNull(item, "item");
            Cleanup();
            return _weakReferences.Any(reference => reference.Target == item);
        }

        /// <summary>
        ///     Copies the elements of the <see cref="WeakReferenceCollection{T}" /> to an
        ///     <see
        ///         cref="T:System.Array" />
        ///     , starting at a particular <see cref="T:System.Array" /> index.
        /// </summary>
        public void CopyTo(T[] array, int arrayIndex)
        {
            Should.NotBeNull(array, "array");
            ToList().CopyTo(array, arrayIndex);
        }

        /// <summary>
        ///     Removes the first occurrence of a specific object from the <see cref="WeakReferenceCollection{T}" />.
        /// </summary>
        public bool Remove(T item)
        {
            Should.NotBeNull(item, "item");
            WeakReference reference = FindWeakReference(item);
            if (reference == null)
                return false;
            return _weakReferences.Remove(reference);
        }

        /// <summary>
        ///     Copies the elements of the <see cref="T:System.Collections.ICollection" /> to an <see cref="T:System.Array" />,
        ///     starting at a particular <see cref="T:System.Array" /> index.
        /// </summary>
        void ICollection.CopyTo(Array array, int index)
        {
            Should.NotBeNull(array, "array");
            ((ICollection)ToList()).CopyTo(array, index);
        }

        /// <summary>
        ///     Gets the number of elements contained in the <see cref="WeakReferenceCollection{T}" />.
        /// </summary>
        public int Count
        {
            get
            {
                Cleanup();
                return _weakReferences.Count;
            }
        }

        /// <summary>
        ///     Gets an object that can be used to synchronize access to the <see cref="T:System.Collections.ICollection" />.
        /// </summary>
        object ICollection.SyncRoot
        {
            get
            {
                if (_syncRoot == null)
                    Interlocked.CompareExchange(ref _syncRoot, new object(), null);
                return _syncRoot;
            }
        }

        /// <summary>
        ///     Gets a value indicating whether access to the <see cref="T:System.Collections.ICollection" /> is synchronized
        ///     (thread safe).
        /// </summary>
        bool ICollection.IsSynchronized
        {
            get { return false; }
        }

        /// <summary>
        ///     Gets a value indicating whether the <see cref="WeakReferenceCollection{T}" /> is read-only.
        /// </summary>
        public bool IsReadOnly
        {
            get { return false; }
        }

        #endregion

        #region Methods

        /// <summary>
        ///     Adds an item to the <see cref="WeakReferenceCollection{T}" />.
        /// </summary>
        public WeakReference Add(T item)
        {
            Should.NotBeNull(item, "item");
            Cleanup();
            var weakReference = new WeakReference(item);
            _weakReferences.Add(weakReference);
            return weakReference;
        }

        /// <summary>
        ///     Searches a weak-reference for the specified item.
        /// </summary>
        public WeakReference FindWeakReference(T item)
        {
            if (item == null)
                return null;
            Cleanup();
            return _weakReferences.FirstOrDefault(reference => reference.Target == item);
        }

        /// <summary>
        ///     Cleans up the dead objects.
        /// </summary>
        public void Cleanup()
        {
            for (int index = 0; index < _weakReferences.Count; index++)
            {
                WeakReference weakReference = _weakReferences[index];
                if (weakReference.IsAlive) continue;
                _weakReferences.Remove(weakReference);
                index--;
            }
        }

        private List<T> ToList()
        {
            Cleanup();
            var items = new List<T>(_weakReferences.Count);
            for (int i = 0; i < _weakReferences.Count; i++)
            {
                WeakReference weakReference = _weakReferences[i];
                var target = (T)weakReference.Target;
                if (target == null)
                    continue;
                items.Add(target);
            }
            return items;
        }

        #endregion
    }
}